﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using PServiceBus.Core.Subscription;
using PServiceBus.Core.Provider;
using PServiceBus.Core.Runtime.Topics;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Core.Logger;
using PServiceBus.Core.Manager;
using PServiceBus.Core.Runtime.Transports;

namespace PServiceBus.Core.Runtime.Subscribers {
    /// <summary>
    /// Collection of Subscribers
    /// </summary>
    public class SubscriberCollection {
        private HashSet<ISubscriber> _addSubscribers = 
            new HashSet<ISubscriber>(
                new FuncEqualityComparer<ISubscriber>((s1,s2) => s1.ID == s2.ID, s => s.Name.GetHashCodeEx()));
        private HashSet<ISubscriber> _updateSubscribers =
            new HashSet<ISubscriber>(
                new FuncEqualityComparer<ISubscriber>((s1, s2) => s1.ID == s2.ID, s => s.Name.GetHashCodeEx()));
        private HashSet<Guid> _deleteIds = new HashSet<Guid>();
        private Dictionary<Guid, HashSet<Guid>> _removedSubscription = new Dictionary<Guid, HashSet<Guid>>();
        public SubscriberCollection() { 
            
        }
        /// <summary>
        /// Create a new subscriber collection
        /// </summary>
        public static SubscriberCollection New { get { return new SubscriberCollection(); }}

        /// <summary>
        /// Add new subscriber information
        /// </summary>
        /// <param name="name"></param>
        /// <param name="id"></param>
        /// <param name="durable"></param>
        /// <param name="transports"></param>
        /// <param name="subscriptions"></param>
        public void Append(ISubscriber subscriber) {
            if (subscriber == null || subscriber.Transports.Count < 1 || subscriber.Subscriptions.Count < 1) return;
            _addSubscribers.Add(subscriber);
        }

        /// <summary>
        /// Add updated information for existing subscriber
        /// </summary>
        /// <param name="name"></param>
        /// <param name="id"></param>
        /// <param name="durable"></param>
        /// <param name="transports"></param>
        /// <param name="subscriptions"></param>
        public void Refresh(ISubscriber subscriber) {
            _updateSubscribers.Add(subscriber);
        }

        public void Delete(IEnumerable<Guid> ids) {
            foreach (var id in ids) _deleteIds.Add(id);
        }

        public void Delete(params Guid[] ids) {
            Delete(ids.AsEnumerable());
        }

        private IEnumerable<ISubscriber> GetNewSubscribers() {
            return _addSubscribers.AsEnumerable();
        }

        private IEnumerable<ISubscriber> GetChangedSubscribers() {
            var subscribers = ObjectManager<ISubscriber>.Instance.Where(s => _updateSubscribers.Any(m => m.ID == s.ID)).ToList();
            foreach (var updateSubscriber in _updateSubscribers) {
                var changed = false;
                var subscriptionCountMatch = 0;
                var transportCountMatch = 0;
                ISubscriber subscriber = subscribers.FirstOrDefault(s => s.ID == updateSubscriber.ID);
                if (subscriber == null) continue;
                foreach (var subscription in subscriber.Subscriptions)
                    if (updateSubscriber.Subscriptions.Any(s => s.ID == subscription.ID && s.MessageFilter == subscription.MessageFilter 
                        && s.NeedMessageHeaders == subscription.NeedMessageHeaders)) 
                        subscriptionCountMatch++;

                if (!(updateSubscriber.Subscriptions.Count == subscriber.Subscriptions.Count && subscriptionCountMatch == updateSubscriber.Subscriptions.Count)) {
                    if (!_removedSubscription.ContainsKey(subscriber.ID)) _removedSubscription[subscriber.ID] = new HashSet<Guid>();
                    foreach (var sup in subscriber.Subscriptions) _removedSubscription[subscriber.ID].Add(sup.ID);
                    subscriber.Subscriptions = updateSubscriber.Subscriptions;
                    changed = true;
                }

                foreach (var transport in subscriber.Transports)
                    if (updateSubscriber.Transports.Any(t => t.Name == transport.Name && t.TopicID == transport.TopicID &&
                        t.Equals(transport))) transportCountMatch++;

                if (!(updateSubscriber.Transports.Count == subscriber.Transports.Count && transportCountMatch == updateSubscriber.Transports.Count)) {
                    subscriber.Transports = updateSubscriber.Transports;
                    changed = true;
                }

                if (subscriber.Durable != updateSubscriber.Durable) {
                    subscriber.Durable = updateSubscriber.Durable;
                    changed = true;
                }

                if (subscriber.Name != updateSubscriber.Name) {
                    subscriber.Name = updateSubscriber.Name;
                    changed = true;
                }
                if (changed) yield return subscriber;
            }
        }

        private void RefreshCollection(IEnumerable<ISubscriber> subscribers, ref SubscriptionCollection mapping, Action<ISubscriber> subAction, Action<SubscriptionCollection, ISubscriber, Guid> mappingAction) {
            foreach (var subscriber in subscribers) {
                if (_removedSubscription.ContainsKey(subscriber.ID)) {
                    var subscriptions = _removedSubscription[subscriber.ID];
                    foreach (var subscriptionID in subscriptions) mappingAction(mapping, subscriber, subscriptionID);
                    _removedSubscription[subscriber.ID].Clear();
                }
                foreach (var subscription in subscriber.Subscriptions) mappingAction(mapping, subscriber, subscription.ID);
                subAction(subscriber);
            }
        }

        /// <summary>
        /// Update changes to collection
        /// </summary>
        public void Save() {
            MethodHelper.TryLog(() =>
            {
                var subscriptionMapping = SubscriptionCollection.New;
                RefreshCollection(GetNewSubscribers(), ref subscriptionMapping,
                    subscriber => {
                        subscriber.Save();
                        ESBLogger.Log("Added {0} as a subscriber", subscriber.Name);
                    }, (mapping, subscriber, topicID) => mapping[topicID, subscriber.ID] = subscriber);

                
                RefreshCollection(GetChangedSubscribers(), ref subscriptionMapping,
                    subscriber => {
                        subscriber.Refresh();
                        ESBLogger.Log("Updated {0} subscription", subscriber.Name);
                    }, (mapping, subscriber, topicID) => mapping[topicID, subscriber.ID] = subscriber.Subscriptions.Count > 0 ? subscriber : null);

                var deletedSubscribers = ObjectManager<ISubscriber>.Instance.Where(s => _deleteIds.Contains(s.ID)).ToList();
                RefreshCollection(deletedSubscribers, ref subscriptionMapping,
                   subscriber =>
                   {
                       subscriber.Delete();
                       ESBLogger.Log("Removed {0} subscription", subscriber.Name);
                   }, (mapping, subscriber, topicID) => mapping[topicID, subscriber.ID] = null);

                SubscriptionCollection.Refresh(subscriptionMapping);
            });
        }
    }
}
